package org.exteen;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.Writer;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.jexl2.JexlEngine;
import org.exteen.lib.CoreTagLibrary;
import org.exteen.lib.TagLibrary;
import org.exteen.output.ExteenStream;
import org.exteen.parser.Node;
import org.exteen.parser.ScriptHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

public class ScriptParser {

    private final static List<? extends TagLibrary> BOOT_LIBRARIES = Arrays.asList(new CoreTagLibrary("exteen:core"));
    private final JexlEngine jexl = new JexlEngine();
    private final Charset charset = Charset.forName("UTF-8");

    private List<? extends TagLibrary> bootLibraries = Collections.unmodifiableList(BOOT_LIBRARIES);

    private ConcurrentHashMap<URL, Node> cache = new ConcurrentHashMap<URL, Node>();
    private ExteenStreamFactory factory = new ExteenStreamFactory();

    public ScriptParser() {
        jexl.setSilent(true);
        jexl.setLenient(true);
    }

    public Script parseString(String xml) throws SAXException, IOException {
        ScriptHandler handler = parseXML(new InputSource(new StringReader(xml)));
        Node node = handler.getRoot();
        //node.optimizeAndGetAsString();
        return new Script(node, null, this);
    }

    public Script parseResource(String resource) throws SAXException, IOException {

        URL url = getClass().getResource(resource);
        if (url==null) {
            throw new ExteenException("Resource " + resource + " not found on classpath");
        }
        return parse(url);
    }

    public Script parse(URL url) throws SAXException, IOException {
        Node node = cache.get(url);
        if (node == null) {
            InputStream is = url.openStream();
            if (is == null) {
                throw new ExteenException("Could not open stream: " + url);
            }
            ScriptHandler handler = null;
            try {
                handler = parseXML(new InputSource(new InputStreamReader(is, charset)));
            } finally {
                is.close();
            }
            node = handler.getRoot();
            //node.optimizeAndGetAsString();
            if (!"false".equalsIgnoreCase((String)node.getAttributeValue("cache", null))) {
                cache.put(url, node);
            }
        }
        return new Script(node, url, this);
    }

    
    private ScriptHandler parseXML(InputSource src) throws IOException, SAXException {
        XMLReader xr = XMLReaderFactory.createXMLReader();
        ScriptHandler handler = new ScriptHandler(jexl);
        xr.setContentHandler(handler);
        xr.parse(src);
        return handler;        
    }

    public List<? extends TagLibrary> getBootLibraries() {
        return bootLibraries;
    }

    public ExteenStream getDefaultStream(Writer w) {
        return factory.buildExteenStream(w);
    }

    public void setExteenStreamFactory(ExteenStreamFactory factory) {
        this.factory = factory;
    }
}
